﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;

namespace Scintilla4Net
{
    /// <summary>
    /// Simple, small and efficient wrapper around
    /// Scintilla editing control
    /// </summary>
    [Docking(DockingBehavior.Ask)]
    public partial class Scintilla : Control
    {

        /// <summary>
        /// Set or get the text inside editor control
        /// </summary>
        public override string Text
        {
            get
            {
                return this.GetText();
            }
            set
            {
                this.SetText(value);
            }
        }


        /// <summary>
        /// Get text lenght inside the editor control
        /// </summary>
        public int Lenght
        {
            get
            {
                return this.GetTextLength();
            }
        }



        /// <summary>
        /// The name of teh dynamic library to load
        /// </summary>
        static string Module = "SciLexer.dll";

        /// <summary>
        /// DLL library handle
        /// </summary>
        static IntPtr hDll = IntPtr.Zero;

        /// <summary>
        /// Scintilla window handle
        /// </summary>
        IntPtr hwndScintilla = IntPtr.Zero;

        /// <summary>
        /// Data pointer for direct scintilla access
        /// </summary>
        IntPtr hScintillaData = IntPtr.Zero;

        /// <summary>
        /// Delegate for direct callbacks to scintilla native control
        /// </summary>
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        delegate int SendMessageDelegate(
            IntPtr ptr, int msg,
            IntPtr wparam, IntPtr lparam
            );
        
        /// <summary>
        /// Send message directly to scintilla (faster!)
        /// </summary>
        SendMessageDelegate SendMsgDlg;


        /// <summary>
        /// static constructor. Load the DLL here
        /// </summary>
        void LoadDll ()
        {
            if (Scintilla.hDll != IntPtr.Zero) return;

            // load the dll
            Scintilla.hDll = Win32.LoadLibrary(Module);
            if (Scintilla.hDll == IntPtr.Zero)
            {
                throw new DllNotFoundException(Module);
            }
        }


        /// <summary>
        /// create new component
        /// </summary>
        public Scintilla()
        {
            // load dynamic link library containing the
            // scintilla component if needed
            LoadDll();

            // hinstance
            var hInstance = Process.GetCurrentProcess().Handle;

            // the parent window handle
            var hwndParent = this.Handle;

            // create the scintilla control
            hwndScintilla = Win32.CreateWindowEx(
                0,
                "Scintilla",
                "", 
                (int)(Win32.Ws.WS_CHILD | Win32.Ws.WS_VISIBLE | Win32.Ws.WS_TABSTOP | Win32.Ws.WS_CLIPCHILDREN),
                0, 0, 0, 0,
                hwndParent, IntPtr.Zero, hInstance, IntPtr.Zero
            );

            // try
            if (hwndScintilla == IntPtr.Zero)
            {
                MessageBox.Show("Something is wrong...");
                return;
            }

            // get the direct function for faster calls to scintilla
            var result = Win32.SendMessage(
                hwndScintilla, (int)Commands.GetDirectFunction, IntPtr.Zero, IntPtr.Zero
                );
            SendMsgDlg = (SendMessageDelegate)Marshal.GetDelegateForFunctionPointer(
                new IntPtr(result),
                typeof(SendMessageDelegate)
                );
            // Scintilla object pointer
            result = Win32.SendMessage(
                hwndScintilla, (int)Commands.GetDirectPointer, IntPtr.Zero, IntPtr.Zero
                );
            hScintillaData = new IntPtr(result);

            // Update the size when needed
            this.SizeChanged += OnSizeChangedEvent;
        }


        /// <summary>
        /// Ensure that all keys work in scintilla as expected
        /// </summary>
        protected override bool IsInputKey(Keys keyData)
        {
            // ignore shift key
            if ((keyData & Keys.Shift) != Keys.None) keyData ^= Keys.Shift;

            // the keys that we like
            switch (keyData)
            {
                case Keys.Tab:  case Keys.Enter:
                case Keys.Up:   case Keys.Down:
                case Keys.Left: case Keys.Right:
                    return true;
            }

            // let the base class decide
            return base.IsInputKey(keyData);
        }



        /// <summary>
        /// Handle scintilla events
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            // not a notification
            if (m.Msg != Win32.WM_NOTIFY) 
            {
                base.WndProc(ref m);
                return;
            }

            // get scintilla notification
            SCNotification scn = (SCNotification)Marshal.PtrToStructure(m.LParam, typeof(SCNotification));

            // ensure it our scintilla component!
            if (scn.nmhdr.hwndFrom != hwndScintilla)
            {
                base.WndProc(ref m);
                return;
            }

            // handle the event ( auto generated method )
            this.HandleEvent(ref m, ref scn);
            m.Result = IntPtr.Zero;
        }


        /// <summary>
        /// Send a message to scintilla
        /// </summary>
        public int SendMessage(int msg, IntPtr wparam, IntPtr lparam)
        {
            return SendMsgDlg(hScintillaData, msg, wparam, lparam);
        }


        /// <summary>
        /// Set window size
        /// </summary>
        void OnSizeChangedEvent(object sender, EventArgs e)
        {
            Win32.SetWindowPos(hwndScintilla, IntPtr.Zero,
                0, 0,
                this.ClientSize.Width, this.ClientSize.Height,
                0);
        }


        /// <summary>
        /// Class for scintilla events
        /// </summary>
        public class ScintillaEventArgs : EventArgs
        {

            /// <summary>
            /// scintilla notification arguments
            /// </summary>
            public SCNotification Native;

            /// <summary>
            /// Create new event
            /// </summary>
            public ScintillaEventArgs(ref SCNotification native)
            {
                Native = native;
            }
        }


        /// <summary>
        /// This matches the Win32 NMHDR structure
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct NotifyHeader
        {
            public IntPtr hwndFrom;	// environment specific window handle/pointer
            public IntPtr idFrom;	// CtrlID of the window issuing the notification
            public uint code;		// The SCN_* notification code
        }

        /// <summary>
        /// Scintilla notification
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct SCNotification
        {
            public NotifyHeader nmhdr;
            public int position;			// SCN_STYLENEEDED, SCN_MODIFIED, SCN_DWELLSTART, SCN_DWELLEND, 
                                            // SCN_CALLTIPCLICK, SCN_HOTSPOTCLICK, SCN_HOTSPOTDOUBLECLICK
            public char ch;					// SCN_CHARADDED, SCN_KEY
            public int modifiers;			// SCN_KEY
            public int modificationType;	// SCN_MODIFIED
            public IntPtr text;				// SCN_MODIFIED
            public int length;				// SCN_MODIFIED
            public int linesAdded;			// SCN_MODIFIED
            public int message;				// SCN_MACRORECORD
            public IntPtr wParam;			// SCN_MACRORECORD
            public IntPtr lParam;			// SCN_MACRORECORD
            public int line;				// SCN_MODIFIED
            public int foldLevelNow;		// SCN_MODIFIED
            public int foldLevelPrev;		// SCN_MODIFIED
            public int margin;				// SCN_MARGINCLICK
            public int listType;			// SCN_USERLISTSELECTION
            public int x;					// SCN_DWELLSTART, SCN_DWELLEND
            public int y;					// SCN_DWELLSTART, SCN_DWELLEND
        }
    }
}
